package com.onpositive.dataminer.userstorage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.onpositive.knowledge.model.IThing;
import com.onpositive.knowledge.model.IUniverse;
import com.onpositive.knowledge.model.values.DescribedValue;
import com.onpositive.semantic.model.api.command.DefaultCommandFactory;
import com.onpositive.semantic.model.api.command.ICommand;
import com.onpositive.semantic.model.api.command.ICommandExecutor;
import com.onpositive.semantic.model.api.command.ICommandFactory;
import com.onpositive.semantic.model.api.command.IHasCommandExecutor;
import com.onpositive.semantic.model.api.command.SimpleOneArgCommand;
import com.onpositive.semantic.model.api.labels.ILabelLookup;
import com.onpositive.semantic.model.api.labels.NotFoundException;
import com.onpositive.semantic.model.api.meta.DefaultMetaKeys;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.property.IPropertyProvider;

public class UserValueStorage implements IPropertyProvider,
		IHasCommandExecutor, ICommandExecutor {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1284724889815653272L;
	protected HashMap<String, HashMap<String, Object>> values = new HashMap<String, HashMap<String, Object>>();

	@Override
	public IProperty getProperty(Object obj, String name) {
		return new UserProperty(name, this);
	}

	@Override
	public Iterable<IProperty> getProperties(Object obj) {
		return Collections.emptySet();
	}

	final IUniverse owner;
	private File file;
	private Thread saver;

	public UserValueStorage(IUniverse owner) {
		this.owner = owner;
	}

	public Object getValue(Object obj, String propId) {
		if (!(obj instanceof IThing)) {
			return null;
		}
		IThing s = (IThing) obj;
		HashMap<String, Object> hashMap = values.get(s.getId());
		if (hashMap != null) {
			return hashMap.get(propId);
		}
		return null;
	}

	public void persist(OutputStream stream) throws SAXException {
		try {
			DocumentBuilder newDocumentBuilder = DocumentBuilderFactory
					.newInstance().newDocumentBuilder();
			try {
				Document parse = newDocumentBuilder.newDocument();
				Element createElement = parse.createElement("values");
				parse.appendChild(createElement);
				for (String s : values.keySet()) {
					Element createElement2 = parse.createElement("object");
					createElement2.setAttribute("id", s);
					HashMap<String, Object> hashMap = values.get(s);
					for (String key : hashMap.keySet()) {
						Element createElement3 = parse.createElement("value");
						createElement3.setAttribute("name", key);
						createElement3.setAttribute("value", hashMap.get(key)
								.toString());
						createElement2.appendChild(createElement3);
					}
					createElement.appendChild(createElement2);
				}
				Transformer newTransformer = TransformerFactory.newInstance()
						.newTransformer();
				newTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
				newTransformer.transform(new DOMSource(parse),
						new StreamResult(stream));
			} catch (Exception e) {
				e.printStackTrace();
			}
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
	}

	public void loadFrom(InputStream stream) {
		try {
			DocumentBuilder newDocumentBuilder = DocumentBuilderFactory
					.newInstance().newDocumentBuilder();
			try {
				Document parse = newDocumentBuilder.parse(stream);
				Element documentElement = parse.getDocumentElement();
				NodeList childNodes = documentElement.getChildNodes();
				for (int a = 0; a < childNodes.getLength(); a++) {
					Node item = childNodes.item(a);
					if (item instanceof Element) {
						Element el = (Element) item;
						String attribute = el.getAttribute("id");
						process(attribute, el);
					}
				}
			} catch (SAXException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}

	}

	private void process(String attribute, Element el) {
		HashMap<String, Object> o = new HashMap<String, Object>();
		NodeList childNodes = el.getChildNodes();
		for (int a = 0; a < childNodes.getLength(); a++) {
			Node item = childNodes.item(a);
			if (item instanceof Element) {
				
				String nodeName = ((Element) item).getAttribute("name");
				String nodeValue = ((Element) item).getAttribute("value");
				IProperty property = owner.getProperty(null, nodeName);
				Object value = null;
				if (property != null) {
					ILabelLookup service = DefaultMetaKeys.getService(property,
							ILabelLookup.class);
					try {
						if (service!=null){
						value = service
								.lookUpByLabel(property, null, nodeValue);
						}
						else{
							value=nodeValue;
						}
					} catch (NotFoundException e) {

					}
				}
				if (value != null) {
					o.put(nodeName, value);
				}
			}
		}
		values.put(attribute, o);
	}

	@Override
	public ICommandFactory getCommandFactory() {
		return DefaultCommandFactory.INSTANCE;
	}

	@Override
	public synchronized void execute(ICommand cmd) {
		if (cmd.getKind().equals(ICommand.SET_VALUE)) {
			SimpleOneArgCommand sm = (SimpleOneArgCommand) cmd;
			IProperty owner = (IProperty) cmd.getOwner();
			String id = owner.getId();
			IThing tg = (IThing) sm.getTarget();
			Object value = sm.getValue();
			if (value instanceof String){
				
				IProperty property = this.owner.getProperty(null, id);
				if (property != null) {
					ILabelLookup service = DefaultMetaKeys.getService(property,
							ILabelLookup.class);
					try {
						value = service!=null? service
								.lookUpByLabel(property, tg, (String) value):value;
						Object value2 = property.getValue(tg);
						if (value2 instanceof DescribedValue){
							DescribedValue vl=(DescribedValue) value2;
							value2=vl.value();
						}
						if (value!=null&&value.equals(value2)){
							return;
						}
						if (value==null&&value2==null){
							return ;
						}					
					} catch (NotFoundException e) {

					}
				}
			}
			//here

			HashMap<String, Object> hashMap = values.get(tg.getId());
			
			if (value != null) {
				if (hashMap == null) {
					hashMap = new HashMap<String, Object>();
					values.put(tg.getId(), hashMap);
				}
				Object object = hashMap.get(id);
				if (value.equals(object)){
					return;
				}
				hashMap.put(id, value);
			} else {
				if (hashMap != null) {
					Object remove = hashMap.remove(value);
					if (remove==null){
						return;
					}
				}
				else{
					return;
				}
				if (hashMap.isEmpty()) {
					values.remove(hashMap);
				}
				
			}
			if (saver==null){
			saver = new Thread() {
				public void run() {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					saver=null;
					doPersist();
					
				}
			};
			saver.start();
			}
		}
	}

	@Override
	public ICommandExecutor getCommandExecutor() {
		return this;
	}

	public void setFile(File fl) {
		this.file = fl;
		if (fl.exists()) {
			try {
				FileInputStream fileInputStream = new FileInputStream(fl);
				try {
					loadFrom(new BufferedInputStream(fileInputStream));
				} finally {
					try {
						fileInputStream.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}

	}

	protected synchronized void doPersist() {
		try {
			if (file != null) {
				FileOutputStream fileOutputStream = new FileOutputStream(file);
				BufferedOutputStream stream = new BufferedOutputStream(
						fileOutputStream);
				try {
					persist(stream);
				} finally {
					try {
						stream.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}
	}

	public boolean isEmpty() {		
		return values.isEmpty() ;
	}
}
